package ro.flashbite.utils.allocator
{
	import flash.utils.Dictionary;
	
	import ro.flashbite.helpers.DictionaryHelpers;
	import ro.flashbite.loggers.ILogger;
	import ro.flashbite.loggers.LoggerFactory;

	/**
	 * 
	 *  Allocator Class
	 * 
	 * 	This class is used to manage creation of multiple objects. Objects created can be reused after, optimizing and minimizing memory 
	 * allocation. In tests, this allocator class can speed up the performance with at least 163%. It is preferable that in the constructor 
	 * of your classes do not use any code. Use instead a init() function with the parameters that you 'want' to pass to the constructor.
	 * 
	 * I). CLASS USAGE : 
	 * 	1.  Create a new instance of Allocator, specifying class to create, the number of instance to create at init, and the max number of 
	 *      instances that the Allocator can create
	 * 			<code>
	 * 				var allocator:IAllocator = new Allocator(MyClass, 0, 30);
	 * 			</code>
	 * 		
	 * 2. 	Use alloc() function to create a new class instance. This instance is stored and mark as reused object. Also if the allocator has
	 * 		saved instances, you will receive them first (that's the most part were speed and memory is optimized).
	 * 			<code>
	 * 				var arrObj:Array = [];
	 * 				for (var i:int = 0; i< 1000; i++) 
	 * 					arrObj.push(allocator.alloc());
	 * 			</code>
	 * 
	 * 3. 	Use free() function to mark a previous created object and that you want to use later
	 * 			<code>
	 * 				for (i = 0; i< 1000; i++)
	 * 					allocator.free(arrObj[i]);
	 * 			</code>
	 * 
	 * 4. 	Use fill() function to create a specific amount of class instances
	 * 			<code>
	 * 				allocator.fill(100);
	 * 			</code>
	 * 
	 * 5. 	After set all object instances created by allocator as reused ( by function free() ), destroy the instance of allocator
	 * 			<code>
	 * 				allocator.destroy();
	 * 				allocator = null;
	 * 			</code>
	 * 
	 *  
	 * II). DIFERENCES BETWEEN OLD CREATION PROCESS AND ALLOCATOR USAGE: 
	 * 	1. Old Usage	
	 * 		// 
	 * 		var arrObj:Array = [];
	 * 		for (var i:int = 1000; i++) {
	 * 			var myInstance:MyClass = new MyClass();
	 * 			myInstance.myInitFunction(...params);
	 * 			arrObj.push(myInstance);
	 * 		}
	 * 		//
	 * 		for (var i:int = 1000; i++) {
	 * 			var myInstance:MyClass = arrObj[i] as MyClass;
	 * 			myInstance.destroy();
	 * 			myInstance = null;
	 * 		}
	 * 
	 * 	2. Allocator Usage
	 *  	// 
	 * 		var allocator:IAllocator = new Allocator( MyClass, 0, 1000 );
	 * 		var arrObj:Array = [];
	 * 		for (var i:int = 1000; i++) {
	 * 			var myInstance:MyClass = allocator.alloc();
	 * 			myInstance.myInitFunction(...params);
	 * 			arrObj.push( myInstance );
	 * 		}
	 * 		//
	 * 		for (var i:int = 1000; i++) {
	 * 			var myInstance:MyClass = arrObj[i] as MyClass;
	 * 			myInstance.destroy();
	 * 			allocator.free(myInstance);
	 * 			myInstance = null;
	 * 		}
	 * 
	  * v 1.2
	 *  
	 * @author Adrian Barbu
	 */	
	public class Allocator implements IAllocator
	{
		/**
		 * default number of maximum instance that Allocator can create; 
		 */
		public static const		MAX_INSTANCES		:	int	= 1000000;
		/**
		 * logger for send messages
		 */
		private 		var 	_logger				:	ILogger;
		/**
		 * internal class type of objects
		 */
		private 		var 	_classType			:	Class;
		/**
		 * number of maximum instance that Allocator can create
		 */
		private 		var 	_maxInstances		:	int;
		/**
		 * number of created instances
		 */
		private 		var 	_indxCreated		:	int;
		/**
		 * number of not used instances
		 */
		private 		var 	_indxNotUsed		:	int;
		/**
		 * vector with instances ready to be used
		 */
		private 		var 	_cachedInstances	:	Vector.<*>;
		/**
		 * array with instances used
		 */
		private 		var 	_usedInstances		:	Dictionary;
		
		// ===========================================================================================================
		// CONSTRUCTOR
		// ===========================================================================================================
		
		/**
		 * Creates an Allocator class
		 * 
		 * @param classToUse 	= the classType to use
		 * @param initialSize	= the Allocator can create this number of objects initially;
		 * @param maxInstances	= the maximum instances that Allocator can create;
		 */
		public function Allocator( classToUse:Class, initialSize:uint = 0, maxInstances:int = MAX_INSTANCES )
		{
			_contructor( classToUse, initialSize, maxInstances );
		}
		private function _contructor(classToUse:Class, initialSize:uint, maxInstances:int):void
		{
			_classType = classToUse;
			_maxInstances = Math.min(maxInstances, MAX_INSTANCES);
			
			_indxCreated = 0;
			_indxNotUsed = 0;						
			
			_cachedInstances = new Vector.<*>(_maxInstances);
			_usedInstances 	 = new Dictionary(false);
			
			_logger = LoggerFactory.getLogger("Allocator");
			
			if (initialSize > 0) 
				fill(initialSize);
		}
		
		// ===========================================================================================================
		// PUBLIC
		// ===========================================================================================================
		
		/**
		 * Creates an object of type specified to Alocator; if they are unused instances, 
		 * the user receive them first; only after he create new ones
		 * 
		 * @return the created object
		 */
		public function allocate():*
		{
			var toReturn:*;
			
			if (_indxNotUsed > 0) {
				//just take one from cache
				_indxNotUsed--;
				toReturn = _cachedInstances[_indxNotUsed];
			} else if (_indxCreated < _maxInstances) {
				//create a new one
				toReturn = new _classType();
				_indxCreated++;	
			} else { throw new Error("I can't create more instances than : " + _maxInstances); }
			
			_usedInstances[toReturn] =  toReturn ;
			
			return toReturn; 
		}
		
		/**
		 * Saves a object for reusing; this object must be created by Allocator
		 * 
		 * @param obj = the object that can be reused; 
		 */
		public function free(obj:*):void
		{
			if (_usedInstances[obj]) {
				// if found in usedInstances
				delete _usedInstances[obj];
				_cachedInstances[_indxNotUsed] = obj;
				_indxNotUsed++;
			}
			else { throw new Error("This instance was not created by this Allocator class"); }
		}
		
		/**
		 * Creates a number of instances of class specified
		 * 
		 * @param increaseCachedBy	= the number of instances to create; 
		 */
		public function fill(increaseCachedBy:uint):void
		{
			if ( (increaseCachedBy + _indxCreated) > _maxInstances ) {
				throw new Error("I can't create more than " + _maxInstances + " instances.");
			} else if (increaseCachedBy > 0){
				//if i can still instanciate
				for (var i:int = 0; i < increaseCachedBy; i++) {
					_cachedInstances[_indxNotUsed] = new _classType();
					_indxCreated++;
					_indxNotUsed++;
				}
			}
		}
		
		// ================================================================================================================================
		// DESTRUCTOR
		// ================================================================================================================================
		
		/**
		 * proper destroy(); removes all elements and show a warning messages if they are still instances in use;
		 */
		public function destroy():void
		{
			var usedIndx:uint = usedCount;
			if (usedIndx > 0) {
				if (_logger) _logger.error("There are still " + usedIndx + " instances in use!");
				else trace("[Allocator][ERROR]trace(There are still " + usedIndx + " instances in use!)");
			}
			
			_classType = null;
			_cachedInstances = null;
			
			if (_usedInstances) {
				DictionaryHelpers.clear(_usedInstances);
				_usedInstances = null;
			}
			
			if (_logger) {
				LoggerFactory.clearLogger(_logger.name);
				_logger = null;
			}
		}
		
		// ================================================================================================================================
		// GETTERS
		// ================================================================================================================================
		
		/**
		 * @return the number of used instances; 
		 */
		public function get usedCount()				: uint 	{ return _indxCreated - _indxNotUsed; 	}
		
		/**
		 * @return the number of allocated and not used instances; 
		 */
		public function get cachedCount()			: uint 	{ return _indxNotUsed; 					}
		
		/**
		 * @return the maximum number of instances that can still be created; 
		 */
		public function get maxAllowedToCreate()	: uint 	{ return _maxInstances - _indxCreated ; }
	}
}